/*
 * Copyright 2017 HomeAdvisor, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *
 */

package com.homeadvisor.kafdrop.config.ini;

import org.apache.commons.lang.StringUtils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;

public class IniFileReader
{
   private static final String COMMENT_CHARS = "#;";
   private static final String SEPARATOR_CHARS = "=:";
   private static final String QUOTE_CHARACTERS = "\"'";
   private static final String LINE_CONT = "\\";
   private static final String LINE_SEPARATOR = "\n";


   public IniFileProperties read(Reader input) throws IOException
   {

      final BufferedReader reader = new BufferedReader(input);

      final IniFileProperties properties = new IniFileProperties();

      String currentSection = null;

      String line;
      while ((line = reader.readLine()) != null)
      {
         line = line.trim();

         if (!isCommentLine(line))
         {
            if (isSectionLine(line))
            {
               currentSection = line.substring(1, line.length() - 1);
            }
            else
            {
               String key = "";
               String value = "";
               int index = findSeparator(line);
               if (index >= 0)
               {
                  key = line.substring(0, index);
                  value = parseValue(line.substring(index + 1), reader);
               }
               else
               {
                  key = line;
               }

               key = StringUtils.defaultIfEmpty(key.trim(), " ");

               properties.addSectionProperty(currentSection, key, value);
            }
         }

      }

      return properties;
   }

   /**
    * Tries to find the index of the separator character in the given string.
    * This method checks for the presence of separator characters in the given
    * string. If multiple characters are found, the first one is assumed to be
    * the correct separator. If there are quoting characters, they are taken
    * into account, too.
    *
    * @param line the line to be checked
    * @return the index of the separator character or -1 if none is found
    */
   private int findSeparator(String line)
   {
      int index = findSeparatorBeforeQuote(line, StringUtils.indexOfAny(line, QUOTE_CHARACTERS));
      if (index < 0)
      {
         index = StringUtils.indexOfAny(line, SEPARATOR_CHARS);
      }
      return index;
   }

   /**
    * Searches for a separator character directly before a quoting character.
    * If the first non-whitespace character before a quote character is a
    * separator, it is considered the "real" separator in this line - even if
    * there are other separators before.
    *
    * @param line       the line to be investigated
    * @param quoteIndex the index of the quote character
    * @return the index of the separator before the quote or &lt; 0 if there is
    * none
    */
   private static int findSeparatorBeforeQuote(String line, int quoteIndex)
   {
      int index = quoteIndex - 1;
      while (index >= 0 && Character.isWhitespace(line.charAt(index)))
      {
         index--;
      }

      if (index >= 0 && SEPARATOR_CHARS.indexOf(line.charAt(index)) < 0)
      {
         index = -1;
      }

      return index;
   }

   private String parseValue(String val, BufferedReader reader) throws IOException
   {
      StringBuilder propertyValue = new StringBuilder();
      boolean lineContinues;
      String value = val.trim();

      do
      {
         boolean quoted = value.startsWith("\"") || value.startsWith("'");
         boolean stop = false;
         boolean escape = false;

         char quote = quoted ? value.charAt(0) : 0;

         int i = quoted ? 1 : 0;

         StringBuilder result = new StringBuilder();
         char lastChar = 0;
         while (i < value.length() && !stop)
         {
            char c = value.charAt(i);

            if (quoted)
            {
               if ('\\' == c && !escape)
               {
                  escape = true;
               }
               else if (!escape && quote == c)
               {
                  stop = true;
               }
               else if (escape && quote == c)
               {
                  escape = false;
                  result.append(c);
               }
               else
               {
                  if (escape)
                  {
                     escape = false;
                     result.append('\\');
                  }

                  result.append(c);
               }
            }
            else
            {
               if (isCommentChar(c) && Character.isWhitespace(lastChar))
               {
                  stop = true;
               }
               else
               {
                  result.append(c);
               }
            }

            i++;
            lastChar = c;
         }

         String v = result.toString();
         if (!quoted)
         {
            v = v.trim();
            lineContinues = lineContinues(v);
            if (lineContinues)
            {
               // remove trailing "\"
               v = v.substring(0, v.length() - 1).trim();
            }
         }
         else
         {
            lineContinues = lineContinues(value, i);
         }
         propertyValue.append(v);

         if (lineContinues)
         {
            propertyValue.append(LINE_SEPARATOR);
            value = reader.readLine();
         }
      } while (lineContinues && value != null);

      return propertyValue.toString();
   }

   private boolean lineContinues(String line)
   {
      return line.trim().endsWith(LINE_CONT);
   }

   /**
    * Tests whether the specified string contains a line continuation marker
    * after the specified position. This method parses the string to remove a
    * comment that might be present. Then it checks whether a line continuation
    * marker can be found at the end.
    *
    * @param line the line to check
    * @param pos  the start position
    * @return a flag whether this line continues
    */
   private boolean lineContinues(String line, int pos)
   {
      String s;

      if (pos >= line.length())
      {
         s = line;
      }
      else
      {
         int end = pos;
         while (end < line.length() && !isCommentChar(line.charAt(end)))
         {
            end++;
         }
         s = line.substring(pos, end);
      }

      return lineContinues(s);
   }

   private boolean isCommentChar(char c)
   {
      return COMMENT_CHARS.indexOf(c) >= 0;
   }

   protected boolean isCommentLine(String line)
   {
      if (line == null)
      {
         return false;
      }
      // blank lines are also treated as comment lines
      return line.length() < 1 || COMMENT_CHARS.indexOf(line.charAt(0)) >= 0;
   }

   protected boolean isSectionLine(String line)
   {
      return line != null && line.startsWith("[") && line.endsWith("]");
   }
}
